package sk.tonyb.library.configuration;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.collections.map.MultiValueMap;
import org.apache.log4j.Logger;

import sk.tonyb.library.projectpaths.ProjectPathDesktop;

/**
 * Class, which provides reading configuration file. <br>
 * Example of using this class: <br>
 * 
 * <code>
 * <br>PropertyConfigurator propertyConfigurator = PropertyConfigurator.getInstance();
 * <br>
 * <br>String s1 = propertyConfigurator.getValue("some.key.1");
 * <br>System.out.println(s1);
 * <br>
 * <br>String[] s2 = propertyConfigurator.getList("some.key.2");
 * <br>for(int i = 0; i < s2.length; i++) {
 * <br> System.out.println(s[i]);
 * <br>}
 * </code>
 * 
 * @author Anton Balucha
 * @since 03.05.2011
 * @last_modified 09.10.2011
 */
public class PropertyConfigurator {

	/** Log4j logger. */
	private static Logger logger = Logger.getLogger(PropertyConfigurator.class);
	
	/** Char, which identify comment. */
	private static final String COMMENT = "#";
	
	/** Chars, which split key and value. */
	private static final String SPLITTER = "=";
	
	/** Singleton. */
	private static PropertyConfigurator propertyConfigurator = null;
	
	/** Configuration file. */
	private String configurationFile = null;
	
	/** Configuration content. */
	private String configurationContent = null;
	
	/** Properties. */
	private MultiValueMap properties = null;
	
	/** Constructor. */
	private PropertyConfigurator() {
		this.initConfigurationFile();
		this.loadPropertyConfiguration();
	}

	/** 
	 * Method, which return instance of PropertyConfigurator - singleton. <br>
	 * 
	 * @return Configurator - unique instance of this class - singleton. 
	 */
	public static synchronized PropertyConfigurator getInstance() {
		
		if(PropertyConfigurator.propertyConfigurator == null) {
			PropertyConfigurator.propertyConfigurator = new PropertyConfigurator();
		}
		
		return PropertyConfigurator.propertyConfigurator;
	}
	
	/** Method, which return configuration content. */
	public synchronized String getConfigurationContent() {
		return this.configurationContent;
	}
	
	/** Method, which return configuration file. */
	public synchronized String getConfigurationFile() {
		return this.configurationFile;
	}
	
	/** Method, which initialize configuration file. */
	private synchronized void initConfigurationFile() {
		this.configurationFile = ProjectPathDesktop.getInstance().getPathConfigurationFile();
	}

	/** Method, which load properties of configuration. */
	private synchronized void loadPropertyConfiguration() {
		
		String line = null;
		StringBuilder content = null;
		String[] keyValue = null;
		MultiValueMap properties = null;

		FileInputStream fileInputStream = null;
		DataInputStream dataInputStream = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader bufferedReader = null;
		
		try {

			content = new StringBuilder();
			properties = new MultiValueMap();
			
			fileInputStream = new FileInputStream(this.configurationFile);
			dataInputStream = new DataInputStream(fileInputStream);
			inputStreamReader = new InputStreamReader(dataInputStream);
			bufferedReader = new BufferedReader(inputStreamReader);

			while ((line = bufferedReader.readLine()) != null) {
				
				line = line.trim();
				content.append(line + "\n");
				
				if(line.startsWith(PropertyConfigurator.COMMENT) == false) {
					
					keyValue = line.split(PropertyConfigurator.SPLITTER);
					
					if(keyValue != null && keyValue.length == 2) {
						properties.put(keyValue[0].trim(), keyValue[1].trim());
					}
				}
			}

			bufferedReader.close();
			inputStreamReader.close();
			dataInputStream.close();
			fileInputStream.close();
		}

		catch(FileNotFoundException e) {
			logger.error("FileNotFoundException", e);
		}
		
		catch(IOException e) {
			logger.error("IOException", e);
		}
		
		catch (Exception e) {
			logger.error("Exception", e);
		}

		this.configurationContent = content.toString();
		this.properties = properties;
	}

	/** 
	 * Method, which return value of first occurence of entered key. <br>
	 *  
	 * @param key - entered key to which will be first value searched. <br>
	 * @return 
	 * value - value of first occurence of entered key. <br>
	 * null - if no value was found or some error occurs. <br>
	 */
	public synchronized String getFirstValue(String key) {
		return this.readValue(key);
	}
	
	/** 
	 * Method, which return values of entered key. <br>
	 * Format of returned string is [value1, value2, .....]. <br>
	 *  
	 * @param key - entered key to which will be values searched. <br>
	 * @return 
	 * value - values of entered key. E.g.: [value1, value2] <br>
	 * null - if no values was found or some error occurs. <br>
	 */
	public synchronized String getValues(String key) {
		return this.readValues(key);
	}
	
	/** 
	 * Method, which return list of values, which belongs to entered key. <br> 
	 * 
	 * @param key - entered key to which will be list of values searched. <br> 
	 * @return 
	 * value - list of values of entered key. <br>
	 * null - if no values was found or some error occurs. <br>
	 */
	public synchronized String[] getList(String key) {
		return this.readList(key);
	}
	
	/** 
	 * Method, which return value of first occurence of entered key. <br>
	 *  
	 * @param key - entered key to which will be value searched. <br>
	 * @return 
	 * value - value of first occurence of entered key. <br>
	 * null - if no value was found or some error occurs. <br>
	 */
	private synchronized String readValue(String key) {
		
		String[] values = this.readList(key);
		
		if(values != null && values.length > 0) {
			return values[0];
		}
		
		else {
			return null;
		}
	}
	
	/** 
	 * Method, which return values of entered key. <br>
	 * Format of returned string is [value1, value2, .....]. <br>
	 *  
	 * @param key - entered key to which will be values searched. <br>
	 * @return 
	 * value - values of entered key. E.g.: [value1, value2] <br>
	 * null - if no value was found or some error occurs. <br>
	 */
	private synchronized String readValues(String key) {
		
		String value = null;
		
		if(key != null && key.trim().isEmpty() == false && this.properties != null && this.properties.size() > 0) {
			value = this.properties.get(key).toString();
		}
		
		return value;
	}
	
	/** 
	 * Method, which reads MultiValueMap and return list of values. <br>
	 * 
	 * @param key - entered key to which will be value searched. <br>
	 * @return 
	 * value - list of values of entered key. <br>
	 * null - if no value was found or some error occurs. <br>
	 */
	private synchronized String[] readList(String key) {
		
		int index = 0;
		String[] values = null;
		Collection collection = null;
		Iterator iterator = null;
		
		if(key != null && key.trim().isEmpty() == false && this.properties != null && this.properties.size() > 0) {
			collection = this.properties.getCollection(key);
			
			if(collection != null && collection.size() > 0) {

				values = new String[collection.size()];
				iterator = collection.iterator();

				while(iterator.hasNext()) {
					values[index++] = (String) iterator.next();
				}
			}
		}
		
		return values;
	}
}
